/*************************************************
 * Copyright (C), 2009-2020, Kong.
 * File name: kh-shell.c
 * Author: Kong       Version: 1.0       Date: 2009.2.26
 * Description:  //用于详细说明此程序文件完成的主要功能，与其他模块
 *                 // 或函数的接口，输出值、取值范围、含义及参数间的控
 *                 // 制、顺序、独立或依赖等关系
 * Others:
 * Function List:  // 主要函数列表，每条记录应包括函数名及功能简要说明
 * History:
*************************************************/

#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "err_.h"
#include "global.h"
#include "syn_analysis.h"
#include "xml.h"

//global variable
//static long int job_sq = 0;
char *reserved_words[] = {
	"<",			//input redirection
	">",			//outputredirection
	"exit",			//exit the shell
	"&",			//running background
	"fg",			//running frontground
	"chgpmt",		//change prompt
	"cd",			//change work directory
};

pid_t job_queue[MAX_JOB];
char *environ_argv[LAST_INDEX];
char config_file[NAME_LEN + 1];

/**
 * init_env:
 * 初始化环境变量
 *************************************************
 * 参数		： 无
 * 返回值		： 无
 */
void init_env()
{
	memset(job_queue, 0, MAX_JOB * sizeof(pid_t));
	//memset(job_queue, 0, MAX_JOB);
	memset(environ_argv, 0, LAST_INDEX);

	snprintf(config_file, NAME_LEN, "%s/%s", getenv("HOME"),
		 CONFIG_FILE_NAME);
	config_file[NAME_LEN] = '\0';

	//申请内存，创建：user, pwd, prompt
	if (get_env_info(config_file, environ_argv) < 0) {		
		environ_argv[PROMPT_INDEX] = calloc(3, sizeof(char));
		*(environ_argv[PROMPT_INDEX] + 0) = '-';
		*(environ_argv[PROMPT_INDEX] + 1) = '>';
	}
	//user
	environ_argv[USER_INDEX] = calloc(MAX_LINE, sizeof(char));
	strncpy(environ_argv[USER_INDEX], getlogin(), MAX_LINE);
	//pwd
	environ_argv[PWD_INDEX] = calloc(NAME_LEN, sizeof(char));
	strncpy(environ_argv[PWD_INDEX], getenv("PWD"), NAME_LEN);

	return;
}

/*static void job_quit(void)
{
    printf("job[%ld]\tDone.\n", job_sq);
}
*/

static volatile sig_atomic_t sig_usr1;
void sigusr1_handler(int);

/**
 * execute_cmd:
 * 执行模块，根据命令、参数和标志位图执行命令
 *************************************************
 * 参数：
 * cmd		: 命令
 * arguments	: 参数二位数组 
 * flags	: 设置标志位图
 * 返回值		： 无
 */
void execute_cmd(char *cmd, char *arguments[], int flags)
{
	#ifdef DEBUG
	//printf("At execute_cmd function, flags: %d\n", flags);
	#endif

	int job_sq;
	pid_t pid;
	int status;
	int fd;

	//case exit
	if (IS_EXIT(flags)) {
		//退出
		//发所有子进程发退出信号
		//code here

		//保存配置
		save_env(config_file, (unsigned char **) environ_argv);
		#ifdef DEBUG
		//printf("Get Token: %s, process is exiting.\n",reserved_words[EXIT_INDEX]);
		#endif
		free(cmd);
		exit(0);
	}
	//case cd
	if (IS_CD(flags)) {
		//改变当前工作目录
		//code here
		
		//改变当前工作目录
		if (chdir(arguments[4]) < 0) {
			err_ret("%s ", PROJECT_NAME);
		}
		
		//更改环境变量
		//if (environ_argv[PWD_INDEX] != NULL) {
		//	free(environ_argv[PWD_INDEX]);
		//}
		//environ_argv[PWD_INDEX] =
		//    calloc(NAME_LEN, sizeof(char));
		if (getcwd(environ_argv[PWD_INDEX], NAME_LEN) == NULL) {
			err_quit("%s: change pwd environment failed", PROJECT_NAME);
		}
        
		#ifdef DEBUG
		//printf("%s %s\n",reserved_words[CD_INDEX], environ_argv[PWD_INDEX]);
		#endif
		return;
	}
	//case chgpmt
	if (IS_CHGPRMT(flags)) {
		//改变提示符
		//code here
		if (environ_argv[PROMPT_INDEX] != NULL) {
			free(environ_argv[PROMPT_INDEX]);
		}
		environ_argv[PROMPT_INDEX] =
		    calloc(strlen(arguments[4]) + 1, sizeof(char));
		strncpy(environ_argv[PROMPT_INDEX], arguments[4],
			strlen(arguments[4]));
		//free(cmd);                    
		#ifdef DEBUG
		//printf("%s %s\n",reserved_words[CHGPRMT_INDEX], environ_argv[PROMPT_INDEX]);
		#endif
		return;
	}
	//case fg       
	if (IS_RUN_FRNTGRND(flags)) {
		//将job调度至前台运行
		//code here
		if (arguments[4] != NULL) {
			job_sq = atoi(arguments[4]) - 1;
		} else {
			for (job_sq = 0;
			     job_sq < MAX_JOB && job_queue[job_sq] == 0;
			     job_sq++);
			if (job_sq == MAX_JOB) {
				err_msg("no job running backgroud");
				return;
			}
		}
		if (job_queue[job_sq] == 0) {
			err_msg("no job running backgroud");
			return;
		}

		if (pid = waitpid(job_queue[job_sq], &status, 0) < 0) {
			err_quit("%s: wait child failed", PROJECT_NAME);
		}
		#ifdef DEBUG
		//if (arguments[4] != NULL)
		//printf("%s %s\n",reserved_words[RUN_FRNTGRND_INDEX], arguments[0]);
		#endif
		return;
	}
	//其他命令
	//code here
	#ifdef DEBUG
	//printf("%s ", cmd);
	//char **ptr = arguments + 4;
	//while ( *ptr != NULL) {
	//      printf("%s ", *ptr);
	//      ptr++;
	//}
	//printf("\n");
	#endif


	if (IS_RUN_BCKGRND(flags)) {
		for (job_sq = 0;
		     job_sq < MAX_JOB && job_queue[job_sq] != 0; job_sq++);
		if (job_sq == MAX_JOB) {
			err_quit("the number of jobs reaches maximum limit");
		}
		printf("job[%d]\t<%s ", job_sq + 1, cmd);
		char **ptr = arguments + 4;
		while (*ptr != NULL) {
			printf("%s ", *ptr);
			ptr++;
		}
		printf("> running background...\n");
		//将子进程PID放入job数组
	}

	if ((pid = fork()) < 0) {
		err_quit("%s: fork error", PROJECT_NAME);
		return;
	} else if (pid == 0) {	//child
		if (IS_RUN_BCKGRND(flags)) {
			//等待父进程讲pid写入job_queue后发送SIGUSR1
			//code here
			//注册信号处理函数，捕捉SIGUSR1信号
			if (signal(SIGUSR1, sigusr1_handler) == SIG_ERR) {
				err_sys("%s: signal (SIGUSR1) error",
					PROJECT_NAME);
			}
			sig_usr1 = 1;
			while (sig_usr1)
				pause();
		}
		
		//恢复信号屏蔽字
		struct sigaction sig_default;
		sig_default.sa_handler = SIG_DFL;
		sigemptyset(&sig_default.sa_mask);
		sig_default.sa_flags = 0;
		if (sigaction(SIGINT, &sig_default, NULL) < 0) {
			err_sys("%s: signal (SIGINT) error", PROJECT_NAME);
		}
		if (sigaction(SIGQUIT, &sig_default, NULL) < 0) {
			err_sys("%s: signal (SIGQUIT) error",
				PROJECT_NAME);
		}
		if (sigaction(SIGTSTP, &sig_default, NULL) < 0) {
			err_sys("%s: signal (SIGTSTP) error",
				PROJECT_NAME);
		}
		
		//输入重定向
		if (IS_INPUT_REDIREC(flags)) {
			if ((fd = open(arguments[0], O_RDONLY)) == -1) {
				err_sys("open file error");
			}
			//重新打开0号文件描述符
			if (fd != STDIN_FILENO) {
				if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) {
					err_sys("dup2 error to stdin");
				}
				close(fd);
			}
		}
		
		//输出重定向
		if (IS_OUTPUT_REDIREC(flags)) {
			//重新打开1号文件描述符
			if ((fd =
			     open(arguments[1],
				  O_WRONLY | O_CREAT | O_TRUNC,
				  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))
			    == -1) {
				err_sys("open file error");
			}
			//重新打开0号文件描述符
			if (fd != STDOUT_FILENO) {
				if (dup2(fd, STDOUT_FILENO) !=
				    STDOUT_FILENO) {
					err_sys("dup2 error to stdout");
				}
				close(fd);
			}
		}
		
		//执行命令
		if (execvp(cmd, arguments + 3) < 0) {
			err_msg("%s: %s: can't find this command",
				PROJECT_NAME, cmd);
			exit(0);
		}
	}

	//parent
	if (IS_RUN_BCKGRND(flags)) {
		//将子进程的pid放入后台运行的job队列
		job_queue[job_sq] = pid;
		//发送信号给子进程
		if (kill(pid, SIGUSR1) < 0) {
			err_sys("send signal to child failed");
		}
		#ifdef DEBUG
		//printf("job[%d] = %d\n", job_sq + 1, job_queue[job_sq] );
		#endif
		//后台运行不等待子进程
		pid = waitpid(pid, &status, WNOHANG);
	}
	//等待子进程
	else
		pid = waitpid(pid, &status, 0);
	if (pid < 0) {
		err_quit("%s: wait child failed", PROJECT_NAME);
	}

	return;
}

/**
 * sigusr1_handler:
 * 信号处理函数，处理SIGUSR1信号，更改全局变量sig_usr1＝0
 *************************************************
 * 参数：
 * sig		: 信号数
 * 返回值		： 无
 */
void sigusr1_handler(int sig)
{
	if (sig == SIGUSR1) {
		sig_usr1 = 0;
	}
}

/**
 * sigchld1_handler:
 * 信号处理函数，处理SIGCHLD信号，如果信号发送者是后台运行的
 * job，则通知用户后台运行程序完成，并将该进程从job队列清出
 *************************************************
 * 参数：
 * sig		: 信号数
 * sig_info	: 保存结束子进程的相关信息的结构体
 * ucontext	: ucontext_t结构指针，本函数暂未使用
 * 返回值		： 无
 */
void sigchld_handler(int sig, siginfo_t * sig_info, void *ucontext)
{
	#ifdef DEBUG
	//printf("get sigchld, pid: %d\t real user id: %d\n", 
	//      sig_info->si_pid, sig_info->si_uid);
	#endif
	int i = 0;
	//printf("job[1]= %d\n", job_queue[0]);
	while (i < MAX_JOB && (job_queue[i] != sig_info->si_pid)) {
		i++;
	}
	if (i != MAX_JOB) {
		printf("job[%d]\tDone!\n", i + 1);
		job_queue[i] = 0;
	}
}

/**
 * main:
 * 主函数，调用初始化shell函数，完成shell主循环
 *************************************************
 * 参数：
 * argc	: 参数个数
 * argv	: 保存参数的二维数组
 * 返回值	： 0～255
 */
int main(int argc, char *argv[])
{
	char buf[MAX_LINE];
	//char cmd[MAX_LINE];  //保存命令
	char *cmd;
	char *arguments[MAX_ARGC];
	int flags = 0;		//存储reserved_words和cmd标志位
	int buf_len;
	int i;


	////////////////////////////////
	//初始化模块
	//设置环境变量
	init_env();

	//设置信号处理函数
	//忽略SIGINT、SIGTSTP和SIGQUIT信号
	struct sigaction ignore, sigchld_act, saveintr, savequit;
	ignore.sa_handler = SIG_IGN;
	sigemptyset(&ignore.sa_mask);
	ignore.sa_flags = 0;

	if (sigaction(SIGINT, &ignore, &saveintr) < 0) {
		err_sys("%s: signal (SIGINT) error", PROJECT_NAME);
	}
	if (sigaction(SIGQUIT, &ignore, &savequit) < 0) {
		err_sys("%s: signal (SIGQUIT) error", PROJECT_NAME);
	}
	if (sigaction(SIGTSTP, &ignore, &savequit) < 0) {
		err_sys("%s: signal (SIGQUIT) error", PROJECT_NAME);
	}
	//捕获SIGCHLD信号
	sigchld_act.sa_sigaction = sigchld_handler;
	sigchld_act.sa_flags = SA_RESTART | SA_SIGINFO;
	if (sigaction(SIGCHLD, &sigchld_act, NULL) < 0) {
		err_sys("%s: signal (SIGCHLD) error", PROJECT_NAME);
	}
	
	//打印提示符
	printf
	    ("\n\tWellcome to Use Kh-shell\n\tVersion: %s\n\tAuthor: %s\n\n%s@%s%s ",
	     VERSION, AUTHOR, environ_argv[USER_INDEX],
	     environ_argv[PWD_INDEX], environ_argv[PROMPT_INDEX]);
	     
	     
	////////////////////////////////
	//while()主循环
	while (fgets(buf, MAX_LINE, stdin) != NULL) {
		buf_len = strlen(buf);
		if (buf[buf_len - 1] == '\n')
			buf[buf_len - 1] = 0;

		////////////////////////////////
		//语法解析器
		cmd = NULL;
		memset(arguments, 0, MAX_ARGC);
		if (syn_analysis(buf, &cmd, arguments, &flags) == -1) {
			//参数错误                  
			goto continue_;
		}
		////////////////////////////////
		//执行模块
		execute_cmd(cmd, arguments, flags);

continue_:
		//标志位清零
		flags = 0;
		//释放内存
		for (i = 0; i < 3; i++) {
			if (arguments[i] != NULL)
				free(arguments[i]);
		}
		for (i = 3; arguments[i] != NULL && i < MAX_ARGC; i++) {
			free(arguments[i]);
		}
		
		//新的提示符
		printf("%s@%s%s ", environ_argv[USER_INDEX], 
			environ_argv[PWD_INDEX],environ_argv[PROMPT_INDEX]);
	}

	return 0;
}
